﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using UnityEngine;
using UnityEngine.SceneManagement;

namespace Puerts
{
    public class JsManager : JsBehaviour, IDisposable
    {
        public Action _jsOnApplicationQuit;
        public Action _jsDispose;
        public Action _jsUpdate;
        public Action _jsLateUpdate;
        public Action _jsFixUpdate;

        static private JsManager _instance = null;
        public string jsLoader = typeof(JsLoader).FullName;
        public bool waitDebugger = false;
        public bool asyncDebugger = false;
        public bool useCommonJS = true;

        public static JsManager Instance
        {
            get
            {
                if (_instance == null)
                {
                    _instance = GameObject.FindObjectOfType<JsManager>();
                    if (_instance)
                        return _instance;
                    GameObject go = new GameObject("[JsEnv]");
                    _instance = go.AddComponent<JsManager>();
                    _instance.waitDebugger = false;
                    _instance.InitJsEnv();
                    Debug.Log("new JsManager instance created.");
                }
                return _instance;
            }
        }

        public JsEnv GetJsEnv()
        {
            return jsEnv;
        }

        protected override void Awake()
        {
            if(_instance != null && _instance != this)
            {
                jsEnvInitialized = false;
                DestroyImmediate(_instance.gameObject);//放置场景里的保持只有一个
            }
            _instance = this;
            if (module == null)
            {
                //场景里没有放JsComponentMgr预制件
                module = TypeScript.CreateFromFile("Launcher");
                module.m_compiled = true;
            }
            InitJsEnv();
            DontDestroyOnLoad(gameObject);
            base.Awake();
        }

        protected override void Start()
        {
            base.Start();
#if UNITY_EDITOR
            HookWatcher();
#endif
        }

        void Update()
        {
            if (jsEnv != null)
                jsEnv.Tick();
            _jsUpdate ?.Invoke();
#if UNITY_EDITOR
            UpdateFileWatcher();
#endif
        }

        void FixedUpdate()
        {
            _jsFixUpdate?.Invoke();
        }
        void LateUpdate()
        {
            _jsLateUpdate?.Invoke();
        }

        async void InitJsEnv()
        {
            //预加载JS ，在JSEnv初始化前调用
            //await ResourceManager.PreloadJS(AddressableConfig.JSLable);
            //调试端口：8848

            ILoader loader = Activator.CreateInstance(Type.GetType(jsLoader)) as ILoader;
            jsEnv = new JsEnv(loader, 8848);
            //插件  "com.tencent.puerts.commonjs":"https://ghproxy.com/https://github.com/puerts/puerts-commonjs.git?path=/upm"
            if (waitDebugger)
            {
                Debug.Log("Debugger Listning on Port:8848");
                if(asyncDebugger)
                    await jsEnv.WaitDebuggerAsync();
                else
                    jsEnv.WaitDebugger();
            }
            if (jsEnv == null)
            {
                Debug.LogError("InitJsEnv null!!!");
            }

            //声明Action： 值类型才需要这样添加
            jsEnv.UsingAction<float>();
            jsEnv.UsingAction<float, float>();
            jsEnv.UsingAction<string, byte[]>();
            jsEnv.UsingAction<Scene, LoadSceneMode>();
            jsEnvInitialized = true;
        }

        public async void StartGame()
        {
            try
            {
                jsEnv.Eval(@"require('bundle')");
            }
            catch (Exception e)
            {
                Debug.LogError(e.ToString());
            }
        }

        public async void Restart()
        {
            Dispose();

            InitJsEnv();
            StartGame();
        }

        private void OnApplicationQuit()
        {
            if (jsEnv != null)
            {
                _jsOnApplicationQuit?.Invoke();
            }
        }

        public void Dispose()
        {
            _jsDispose?.Invoke();
            if (jsEnv != null)
            {
                try
                {
                    jsEnv.Dispose();
                    jsEnv = null;
                    jsEnvInitialized = false;
                }
                catch (Exception ex)
                {
                    string msg = string.Format("js exception : {0}\n {1}", ex.Message, ex.StackTrace);
                    Debug.LogError(msg, null);
                }
            }
        }

        protected override void OnDestroy()
        {
            base.OnDestroy();            
            _jsUpdate = null;
            _jsLateUpdate = null;
            _jsFixUpdate = null;
#if UNITY_EDITOR
            UnHookWatcher();
#endif
        }
#if UNITY_EDITOR
        //  ************* JsFileWatcher ****************

        #region JsFileWatcher

        private Dictionary<string, string> jscache = new Dictionary<string, string>();
        private FileSystemWatcher m_Watcher;

        // Update is called once per frame
        private int watcherTick = 0;

        void UpdateFileWatcher()
        {
            if (jscache.Count != 0)
            {
                if (watcherTick <= 0)
                {
                    var m = jscache.First();
                    jscache.Remove(m.Key);
                    this.ClearAModuleCache(m.Key,m.Value);
                    Debug.Log("clearAModulecache : "+m.Key + " , "+m.Value);
                    watcherTick = 15;
                }

                watcherTick -= 1;
            }
        }

        private void HookWatcher()
        {
            m_Watcher = new FileSystemWatcher(JsLoader.PathJs, "*.js");
            m_Watcher.NotifyFilter = NotifyFilters.LastWrite;
            m_Watcher.IncludeSubdirectories = true;
            //m_Watcher.Created += new FileSystemEventHandler(); //Add to the solution before compile
            //m_Watcher.Renamed += new FileSystemEventHandler(); //Rename to the solution before compile
            //m_Watcher.Deleted += new FileSystemEventHandler(); //Remove to the solution before compile
            m_Watcher.Changed += OnJsChanged;
            m_Watcher.EnableRaisingEvents = true;
        }

        private void UnHookWatcher()
        {
            m_Watcher.EnableRaisingEvents = false;
            m_Watcher.Changed -= OnJsChanged;
            m_Watcher.Dispose();
        }

        private void OnJsChanged(object sender, FileSystemEventArgs s)
        {
            string p = s.FullPath.Replace("\\", "/");
            string w = m_Watcher.Path.Replace("\\", "/");
            string module =  p.Substring(w.Length).Split('.')[0];
            if (jscache.ContainsKey(module))
                return;
            jscache.Add(module, p.Substring(w.Length));
        }
        public void ClearAModuleCache (string module,string key)
        {
            jsEnv.Eval($"global.clearAModuleCache('{module}','{key}')");
        }
        #endregion

#endif
    }

}